{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Anomaly Detection Tutorial\n",
    "\n",
    "This guide will show how to use Tribuo’s anomaly detection models to find anomalous events in a toy dataset drawn from a mixture of Gaussians. We'll discuss the options in the LibSVM anomaly detection algorithm (using a one-class nu-SVM) and discuss evaluations for anomaly detection tasks.\n",
    "\n",
    "## Setup\n",
    "\n",
    "We'll load in a jar and import a few packages."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "%jars ./tribuo-anomaly-libsvm-4.0.0-jar-with-dependencies.jar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import org.tribuo.*;\n",
    "import org.tribuo.util.Util;\n",
    "import org.tribuo.anomaly.*;\n",
    "import org.tribuo.anomaly.evaluation.*;\n",
    "import org.tribuo.anomaly.example.AnomalyDataGenerator;\n",
    "import org.tribuo.anomaly.libsvm.*;\n",
    "import org.tribuo.common.libsvm.*;"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "var eval = new AnomalyEvaluator();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Dataset\n",
    "Tribuo's anomaly detection package comes with a simple data generator that emits pairs of datasets containing 5 features. The training data is free from anomalies, and each example is sampled from a 5 dimensional gaussian with fixed mean and diagonal covariance. The test data is sampled from a mixture of two distributions, the first is the same as the training distribution, and the second uses a different mean for the gaussian (keeping the same covariance for simplicity). All the data points sampled from the second distribution are marked `ANOMALOUS`, and the other points are marked `EXPECTED`. These form the two classes for Tribuo's anomaly detection system. You can also use any of the standard data loaders to pull in anomaly detection data.\n",
    "\n",
    "The libsvm anomaly detection algorithm requires there are no anomalies in the training data, but this is not required in general for Tribuo's anomaly detection infrastructure.\n",
    "\n",
    "We'll sample 2000 points for each dataset, and 20% of the test set will be anomalies."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "var pair = AnomalyDataGenerator.gaussianAnomaly(2000,0.2);\n",
    "var data = pair.getA();\n",
    "var test = pair.getB();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model Training\n",
    "We'll fit a one-class SVM to our training data, and then use that to determine what things in our test set are anomalous. We'll use an [RBF Kernel](https://en.wikipedia.org/wiki/Radial_basis_function_kernel), and set the kernel width to 1.0."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "var params = new SVMParameters<>(new SVMAnomalyType(SVMAnomalyType.SVMMode.ONE_CLASS), KernelType.RBF);\n",
    "params.setGamma(1.0);\n",
    "params.setNu(0.1); \n",
    "var trainer = new LibSVMAnomalyTrainer(params);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Training is the same as other Tribuo prediction tasks, just call train and pass the training data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "*\n",
      "optimization finished, #iter = 692\n",
      "obj = 293.8182352369252, rho = 3.201748862633537\n",
      "nSV = 301, nBSV = 120\n",
      "\n",
      "Training took (00:00:00:146)\n"
     ]
    }
   ],
   "source": [
    "var startTime = System.currentTimeMillis();\n",
    "var model = trainer.train(data);\n",
    "var endTime = System.currentTimeMillis();\n",
    "System.out.println();\n",
    "System.out.println(\"Training took \" + Util.formatDuration(startTime,endTime));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Unfortunately the LibSVM implementation is a little chatty and insists on writing to standard out, but after that we can see it took about 140ms to run (on my 2020 16\" Macbook Pro, you may get slightly different runtimes). We can check how many support vectors are used by the SVM, from the training set of 2000:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "301"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "((LibSVMAnomalyModel)model).getNumberOfSupportVectors()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So we used 301 datapoints to model the density of the expected data."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model evaluation\n",
    "Tribuo's infrastructure treats anomaly detection as a binary classification problem with the fixed label set {`EXPECTED`,`ANOMALOUS`}. When we have ground truth labels we can thus measure the true positives (anomalous things predicted as anomalous), false positives (expected things predicted as anomalous), false negatives (anomalous things predicted as expected) and true negatives (expected things predicted as expected), though the latter number is not usually that useful. We can also calculate the usual summary statistics: precision, recall and F1 of the anomalous class. We're going to compare against the ground truth labels from the data generator."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AnomalyEvaluation(tp=421 fp=250 tn=1329 fn=0 precision=0.627422 recall=1.000000 f1=0.771062)\n",
      "              EXPECTED  ANOMALOUS\n",
      "EXPECTED         1,329        250\n",
      "ANOMALOUS            0        421\n",
      "\n"
     ]
    }
   ],
   "source": [
    "var testEvaluation = eval.evaluate(model,test);\n",
    "System.out.println(testEvaluation.toString());\n",
    "System.out.println(testEvaluation.confusionString());"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can see that the model has no false negatives, and so perfect recall, but has a precision of 0.62, so approximately 62% of the positive predictions are true anomalies. This can be tuned by changing the width of the gaussian kernel which changes the range of values which are considered to be expected. The confusion matrix presents the same results in a more common form for classification tasks.\n",
    "\n",
    "We plan to further expand Tribuo's anomaly detection functionality to incorporate other algorithms in the future."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Java",
   "language": "java",
   "name": "java"
  },
  "language_info": {
   "codemirror_mode": "java",
   "file_extension": ".jshell",
   "mimetype": "text/x-java-source",
   "name": "Java",
   "pygments_lexer": "java",
   "version": "14+36-1461"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
